﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CrateMayhem.Common.Data;
using CrateMayhem.Common.Model;
using CrateMayhem.Common.Services;

namespace CrateMayhem.Common.Controls
{
    public class GameGridManager
    {
        private readonly IGridContainer container;
        private readonly IGameService gameService; 
        private readonly Stack<TrackedItem> moves = new Stack<TrackedItem>();

        public const int CrateSize = 34;
        public const int OverallMaxRows = 20;
        public const int OverallMaxCols = 20;

        public GameGridManager(
            IGridContainer container, 
            IServiceFactory serviceFactory, 
            IWorldRepository worldRepository)
        {
            this.container = container;
            gameService = serviceFactory.Create<IGameService>();
            gameService.Push += OnPush;
            gameService.Undo += OnUndo;
            Worlds = new List<World>(worldRepository.LoadAll());
        }

        public IEnumerable Children { get; set; }

        public IEnumerable<World> Worlds { get; private set; } 

        private IWorker Worker
        {
            get { return Children.OfType<IWorker>().First(); }
        }

        public void ClearMoves()
        {
            moves.Clear();
        }

        public void HighlightCratesOnTarget()
        {
            var crateTargets = Children.OfType<ICrateTarget>().ToList();
            var crates = Children.OfType<ICrate>().ToList();

            foreach (var crate in crates)
            {
                var crateRow = container.GetRow(crate);
                var crateCol = container.GetColumn(crate);

                foreach (var target in crateTargets)
                {
                    var targetRow = container.GetRow(target);
                    var targetCol = container.GetColumn(target);

                    if (crateRow == targetRow && crateCol == targetCol)
                    {
                        crate.UpdateBrush(true);
                        break;
                    }

                    crate.UpdateBrush(false);
                }
            }
        }

        private void OnPush(object sender, PushEventArgs e)
        {
            var dc = 0;
            var dr = 0;
            if (e.Move == MoveType.Left || e.Move == MoveType.Right)
                dc = e.Move == MoveType.Right ? 1 : -1;
            else if (e.Move == MoveType.Up || e.Move == MoveType.Down)
                dr = e.Move == MoveType.Down ? 1 : -1;

            var pushed = Move(dr, dc);
            gameService.RaisePushCompleted(pushed, e.Move);
            HighlightCratesOnTarget();
            GameComplete();
        }

        private void OnUndo(object sender, PushEventArgs e)
        {
            var dc = 0;
            var dr = 0;
            if (e.Move == MoveType.Left || e.Move == MoveType.Right)
                dc = e.Move == MoveType.Right ? 1 : -1;
            else if (e.Move == MoveType.Up || e.Move == MoveType.Down)
                dr = e.Move == MoveType.Down ? 1 : -1;

            var pushed = Undo(dr, dc);
            gameService.RaisePushCompleted(pushed, e.Move);
            HighlightCratesOnTarget();
        }

        private IMapControl FindChildIn(int row, int col)
        {
            return Children
                .Cast<IMapControl>()
                .FirstOrDefault(c => !(c is ICrateTarget) 
                    && container.GetRow(c) == row 
                    && container.GetColumn(c) == col);
        }

        private bool Move(int dr, int dc)
        {
            var worker = Worker;
            var row = container.GetRow(worker);
            var col = container.GetColumn(worker);

            var item = FindChildIn(row + dr, col + dc);

            if (item is IBrick) return false;

            if (item != null)
            {
                var nextItem = FindChildIn(row + 2 * dr, col + 2 * dc);

                if (nextItem is IBrick || nextItem is ICrate) return false;

                moves.Push(new TrackedItem((ICrate)item, row + dr, col + dc));
                container.SetRow(item, row + 2 * dr);
                container.SetColumn(item, col + 2 * dc);
            }
            else
            {
                moves.Push(TrackedItem.Empty);
            }

            container.SetRow(worker, row + dr);
            container.SetColumn(worker, col + dc);

            return true;
        }

        private bool Undo(int dr, int dc)
        {
            if (moves.Count == 0) return false;

            var worker = Worker;
            var row = container.GetRow(worker);
            var col = container.GetColumn(worker);

            var item = moves.Pop();

            if (item != TrackedItem.Empty)
            {
                container.SetRow(item.Crate, item.Row);
                container.SetColumn(item.Crate, item.Column);
            }

            container.SetRow(worker, row - dr);
            container.SetColumn(worker, col - dc);

            return true;
        }

        private void GameComplete()
        {
            if (IsFinished())
            {
                gameService.RaiseFinished();
            }
        }

        private bool IsFinished()
        {
            var crateTargets = Children.OfType<ICrateTarget>().ToList();
            var crates = Children.OfType<ICrate>().ToList();

            foreach (var crate in crates)
            {
                var crateRow = container.GetRow(crate);
                var crateCol = container.GetColumn(crate);
                var located = false;

                foreach (var target in crateTargets)
                {
                    var targetRow = container.GetRow(target);
                    var targetCol = container.GetColumn(target);

                    if (crateRow == targetRow && crateCol == targetCol)
                    {
                        located = true;
                        break;
                    }
                }

                if (!located) return false;
            }

            return true;
        }
    }
}